import { Length } from "../units";
import { Visibility } from "../common";
import { ObservedMap } from "../../libs/ObservedMap";

export type Callback<T> = (callback: T) => void;
interface IListenerOption {
    listener: ((this: HTMLElement, ev: keyof HTMLElementEventMap) => any) | EventListenerOrEventListenerObject;
    options?: boolean | AddEventListenerOptions;
}
export abstract class IComponent {
    _id: string;
    protected _width: string = '';
    protected _height: string = '';
    protected _children: Array<IComponent> = [];
    protected _backgroundColor?: string = undefined;
    protected _borderRadius: string | undefined = undefined;
    protected _padding: string | undefined = undefined;
    protected _classes: string[] = [];
    private preBuild?: (elem: HTMLElement) => void = undefined;
    private _visibility: Visibility = Visibility.Visible;
    private _ele?: HTMLElement;
    public position: string = 'relative';

    protected constructor(id: string) {
        this._id = id;
    }

    visibility(value: Visibility): this {
        this._visibility = value;
        return this;
    }

    backgroundColor(value: string | undefined = undefined): this {
        this._backgroundColor = value;
        return this;
    }

    id(id: string): this {
        this._id = id;
        return this;
    }

    children(...children: Array<any>): this {
        this._children = children;
        return this;
    }


    private listenerBindMap: Map<string, IListenerOption> = new Map;
    on<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): this;
    on(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): this {
        this.listenerBindMap.set(type, { listener, options });
        return this;
    }

    width(width: Length): this {
        width = this.handleStyleNumber(width);
        const el = document.getElementById(this._id);
        if (el === null) {
            this._width = width;
            return this;
        }
        el.style.width = width;
        return this;
    }

    transition(): this {
        return this;
    }

    height(height: Length): this {
        height = this.handleStyleNumber(height);
        const el = document.getElementById(this._id);
        if (el == null) {
            this._height = height;
            return this;
        }
        el.style.height = height;
        return this;
    }

    build(_parentId?: string): void {
    }

    borderRadius(value: string | number): this {
        this._borderRadius = this.handleStyleNumber(value);
        return this;
    }

    padding(value: string | number): this {
        this._padding = this.handleStyleNumber(value);
        return this;
    }

    classes(...classes: string[]): this {
        this._classes = classes;
        return this;
    }

    remove() {
        this._ele && this._ele.remove();
    }

    protected _build(element: HTMLElement, parentId?: string): void {
        const old = ObservedMap.componentTree.stack.find(c => c._id == this._id);
        old && old.remove();
        element.remove();
        if (this.preBuild != undefined) {
            this.preBuild(element);
        }
        for (const [key, l] of this.listenerBindMap) {
            element.addEventListener(key as any, l.listener as any, l.options);
        }
        element.style.position = this.position;
        element.style.transitionDuration = '0s';
        element.style.boxSizing = 'border-box';
        element.classList.add(...this._classes);
        if (this._visibility == Visibility.None) {
            element.style.display = 'none';
        } else {
            element.style.visibility = this._visibility;
        }
        if (this._backgroundColor) {
            element.style.backgroundColor = this._backgroundColor;
        }
        if (this._borderRadius) {
            element.style.borderRadius = this._borderRadius;
        }
        if (this._padding) {
            element.style.padding = this._padding;
        }
        this._ele = element;
        if (parentId !== undefined) {
            const parentNode = document.getElementById(parentId);
            if (parentNode == null) return;
            parentNode.appendChild(element);
        } else {
            document.body.appendChild(element);
            ObservedMap.componentTree.push(this);
        }
        if (this._children.length > 0) {
            for (const child of this._children) {
                if (child == undefined) continue;
                child.preBuild = el => el.style.position = 'relative';
                child.build(this._id);
            }
        }
    }

    protected handleStyleNumber(val: number | string): string {
        if (typeof val === "number") {
            val = val + 'px';
        }
        return val;
    }
}